Acala Swap Performance Summary

Active Users
Swap Trades
Trades Per User
Trading Volume
Count U_Growth U_Trend S_Growth S_Trend TPU_Growth TPU_Trend V_Growth V_Trend
ALL 41974 0.56 0.51 0.94 0.42
ACA:AUSD 16199 0.55 0.49 0.86 0.38
AUSD:LCDOT 13234 0.52 0.52 1.06 0.44
DOT:LCDOT 12436 0.52 0.54 1.09 0.44
AUSD:LDOT 105 NA NA NA NA

Last updated: 2022-05-18 07:47:15

Date range of data: 2022-05-04T00:01:30.304 to 2022-05-17T23:59:42.585.

Sources:

Swaps:

Loans:

---
title: "Acala / Karura Dashboards"
output:
  flexdashboard::flex_dashboard:
    orientation: rows
    vertical_layout: scroll
    social: menu
    source_code: embed
params:
  network: Karura
  window: 14
---

```{css custom1, echo=FALSE}
.dataTables_scrollBody {
    max-height: 100% !important;
}
```

```{r global, include=FALSE}

knitr::opts_chunk$set(
  message = FALSE,
  warning = FALSE,
  comment = "#>"
)

library(kableExtra)
library(formattable)
library(lubridate)
library(flexdashboard)
library(DT)

# Helper function to concat
`%+%` <- function(a, b) paste0(a, b)

# tokens <- rbind(c("ACA", "Acala", 12),
#                 c("AUSD","Acala Dollar", 12),
#                 c("taiKSM","Taiga KSM", 12),
#                 c("RMRK","RMRK", 10),
#                 c("DOT","Polkadot", 10),
#                 c("LCDOT","Liquid Crowdloan DOT", 10),
#                 c("LDOT","Liquid DOT", 10),
#                 c("RENBTC","Ren Protocol BTC", 8),
#                 c("CASH","Compound CASH", 8),
#                 c("KAR","Karura", 12),
#                 c("KUSD","Karura Dollar", 12),
#                 c("KSM","Kusama", 12),
#                 c("LKSM","Liquid KSM", 12),
#                 c("TAI","Taiga", 12),
#                 c("BNC","Bifrost Asgard", 12),
#                 c("VSKSM","Bifrost Voucher Slot KSM", 12),
#                 c("PHA","Phala Native Token", 12),
#                 c("KINT","Kintsugi Native Token", 12),
#                 c("KBTC","Kintsugi Wrapped BTC", 8)) %>%
#   as.data.table %>%
#   setnames(c("Token","Name","decimals"))


# remotes::install_github("ropensci/ghql") # if package is not already installed
library(jsonlite)
library(data.table)
library(ghql)
x <- GraphqlClient$new()


window <- params$window
endpoint <- params$endpoint
network <- params$network

if (network == 'Acala') {
  
  dex_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/acala-dex"
  loan_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/acala-loans"
  swap_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/acala-swap-day-data"
  official_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/acala"
  
} else {

  dex_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/karura-dex"
  loan_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/karura-loan"
  swap_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/karura-test"
  official_endpoint <- "https://api.subquery.network/sq/AcalaNetwork/karura"

}

getSwaps <- function(endpoint, window) {
  # endpoint <- official_endpoint; window <- 10
  
  # make a client
  cli <- GraphqlClient$new(url = endpoint)
  mindate <- today(tzone = 'UTC') - window
  
  cursor <- ''
  resList <- list()
  for (i in 1:1000) {
    if (cursor == '') {
      cursorStr <- 'first:100'
    } else {
      cursorStr <- 'first:100 after:"' %+% cursor %+% '"'
    }
    qry <- Query$new()
    qry$query('dexActions', '
    {
      query {
        dexActions (filter: {timestamp: {greaterThanOrEqualTo: "' %+% mindate %+% '"}, type: {equalTo: "swap"}} ' %+% cursorStr %+% ') {
          totalCount
          edges {
            node { timestamp id accountId token0Id token1Id  volumeUSD data
            }
            cursor
          }
          pageInfo {
            endCursor
            hasNextPage
          }
        }
      }
    }')
    result <- cli$exec(qry$queries$dexActions)  %>%
      fromJSON(flatten=TRUE)
    cursor <- result$data$query$dexActions$pageInfo$endCursor
    res <- as.data.table(result$data$query$dexActions$edges)
    res[, cursor := NULL]
    
    print(i %+% " " %+% nrow(res))
    resList[[i]] <- res
    if (result$data$query$dexActions$pageInfo$hasNextPage == FALSE) break
  }
  res <- rbindlist(resList)
  setnames(res, old = names(res), new = gsub("node.", "", names(res)))
  
  if (substr(max(res$timestamp), 12, 13) < 23) {
    maxdate <- as.Date(max(res$timestamp))-1
  } else {
    maxdate <- as.Date(max(res$timestamp))
  }
  
  res <- res[timestamp <= maxdate]
  res[, date := as.Date(timestamp)]
  setorder(res, timestamp)
  
  # Replace foreign assets
  res[, token0Id := subscanr::fixToken(token0Id)]
  res[, token1Id := subscanr::fixToken(token1Id)]

  # Normalize pairs
  res[, pair := paste0(token0Id %+% ":" %+% token1Id)]
  res[token1Id < token0Id, pair := paste0(token1Id %+% ":" %+% token0Id)]
  res[, exclude := token0Id == token1Id]
  res  
  
}

getDailyPoolsQuery <- function(endpoint, window) {
  # endpoint <- dex_endpoint; window <- 40
  
  # make a client
  cli <- GraphqlClient$new(url = endpoint)
  mindate <- today(tzone = 'UTC') - window
  
  cursor <- ''
  resList <- list()
  for (i in 1:1000) {
    if (cursor == '') {
      cursorStr <- 'first:100'
    } else {
      cursorStr <- 'first:100 after:"' %+% cursor %+% '"'
    }
    qry <- Query$new()
    qry$query('dailyPools', '
      {
        query {
          dailyPools (filter: {timestamp: {greaterThanOrEqualTo: "' %+% mindate %+% '"}} ' %+% cursorStr %+% ') {
            totalCount
            edges {
              node { 
                timestamp token0 {id} token1 {id} feeRateUSD dailyTradeVolumeUSD totalTVL txCount
              }
              cursor
            }
            pageInfo {
              endCursor
              hasNextPage
            }
          }
        }
      }')
      result <- cli$exec(qry$queries$dailyPools)  %>%
        fromJSON(flatten=TRUE)
    
      cursor <- result$data$query$dailyPools$pageInfo$endCursor
      res <- as.data.table(result$data$query$dailyPools$edges)
      res[, cursor := NULL]
      
      print(i %+% " " %+% nrow(res))
      resList[[i]] <- res
      if (result$data$query$dailyPools$pageInfo$hasNextPage == FALSE) break
  }
  res <- rbindlist(resList)
  setnames(res, old = names(res), new = gsub("node.", "", names(res)))
  res[, date := as.Date(timestamp)]
  setorder(res, date)
  setnames(res, 
           c("token0.id","token1.id","dailyTradeVolumeUSD","totalTVL","feeRateUSD"), 
           c("token0Id", "token1Id", "volumeUSD", "tvlUSD", "feeUSD"))

  # Replace foreign assets
  res[, token0Id := subscanr::fixToken(token0Id)]
  res[, token1Id := subscanr::fixToken(token1Id)]

  # Normalize pairs
  res[, pair := paste0(token0Id %+% ":" %+% token1Id)]
  res[token1Id < token0Id, pair := paste0(token1Id %+% ":" %+% token0Id)]
  
  res[, feeUSD := as.numeric(feeUSD) / 1e18]
  res[, volumeUSD := as.numeric(volumeUSD) / 1e18]
  res[, tvlUSD := as.numeric(tvlUSD) / 1e18]
  
  res  
  
}


liquidityQuery <- function(endpoint, window) {
  method <- 'dexActions'; maxn <- 1000

  cli <- GraphqlClient$new(url = endpoint)
  mindate <- today(tzone = 'UTC') - window
  
  cursor <- ''
  resList <- list()
  for (i in 1:maxn) {
    if (cursor == '') {
      cursorStr <- 'first:100'
    } else {
      cursorStr <- 'first:100 after:"' %+% cursor %+% '"'
    }
    qry <- Query$new()
    qry$query(method, '
      {
        query {
          ' %+% method %+% ' (filter: {timestamp: {greaterThanOrEqualTo: "' %+% mindate %+% '"}, type: {in: ["addLiquidity","removeLiquidity"]}} ' %+% cursorStr %+% ') {
            totalCount
            edges {
              node { timestamp id nodeId accountId type token0Id token1Id token0Amount token1Amount volumeUSD
              }
              cursor
            }
            pageInfo {
              endCursor
              hasNextPage
            }
          }
        }
      }')
    result <- cli$exec(qry$queries[[method]])  %>%
      fromJSON(flatten=TRUE)
    cursor <- result$data$query[[method]]$pageInfo$endCursor
    res <- as.data.table(result$data$query[[method]]$edges)
    res[, cursor := NULL]
    
    print(i %+% " " %+% nrow(res))
    resList[[i]] <- res
    if (result$data$query[[method]]$pageInfo$hasNextPage == FALSE) break
  }
  res <- rbindlist(resList) %>% 
    setnames(names(res), gsub("node.", "", names(res)))
  res[, date := as.Date(timestamp)]
  setorder(res, date)
  
  # Replace foreign assets
  res[, token0Id := subscanr::fixToken(token0Id)]
  res[, token1Id := subscanr::fixToken(token1Id)]

  # Normalize pairs
  res[, pair := paste0(token0Id %+% ":" %+% token1Id)]
  res[token1Id < token0Id, pair := paste0(token1Id %+% ":" %+% token0Id)]
  res  
  
}

getLoansCollateralParamsQuery <- function(endpoint) {
  method <- 'collateralParams'; maxn <- 1000
  
  cli <- GraphqlClient$new(url = endpoint)

  qry <- Query$new()
  qry$query(method, '
    {
      query {
     	collateralParams {
          totalCount
            nodes { 
							collateral {
							  id
							} maximumTotalDebitValue interestRatePerSec liquidationRatio 
              liquidationPenalty requiredCollateralRatio 
          }
        }
      }
    }')
  result <- cli$exec(qry$queries[[method]])  %>%
    fromJSON(flatten=TRUE)
  res <- as.data.table(result$data$query[[method]]$nodes)

  # Replace foreign assets
  res[, collateral.id := subscanr::fixToken(collateral.id)]
  res <- merge(res, subscanr::tokens, by.x='collateral.id', by.y='Token')

  res[, adj := as.numeric(substr(as.character(1e20),1, as.numeric(decimals) + 1))]
  res[, maximumTotalDebitValue := as.numeric(maximumTotalDebitValue) / as.numeric(adj)]  
  res[, liquidationRatio := as.numeric(liquidationRatio) / 1e18]  
  res[, liquidationPenalty := as.numeric(liquidationPenalty) / 1e18]  
  res[, requiredCollateralRatio := as.numeric(requiredCollateralRatio) / 1e18]  
  res[, APR := (as.numeric(interestRatePerSec) / 1e18 + 1) ** (365 * 86400) - 1]
  res

}

getLoansDailyPositionsQuery <- function(endpoint, window) {
  # endpoint <-loan_endpoint
  method <- 'dailyPositions'; maxn <- 1000

  cli <- GraphqlClient$new(url = endpoint)
  mindate <- today(tzone = 'UTC') - window
  
  cursor <- ''
  resList <- list()
  for (i in 1:maxn) {
    if (cursor == '') {
      cursorStr <- 'first:100'
    } else {
      cursorStr <- 'first:100 after:"' %+% cursor %+% '"'
    }
    qry <- Query$new()
    qry$query(method, '
      {
        query {
          ' %+% method %+% ' (filter: {timestamp: {greaterThanOrEqualTo: "' %+% mindate %+% '"}} ' %+% cursorStr %+% ') {
            totalCount
            edges {
              node { 
               id owner {id} collateral {id} depositAmount debitAmount depositVolumeUSD debitVolumeUSD 
               depositChangedUSD debitChangedUSD debitExchangeRate timestamp txCount
              }
              cursor
            }
            pageInfo {
              endCursor
              hasNextPage
            }
          }
        }
      }')
    result <- cli$exec(qry$queries[[method]])  %>%
      fromJSON(flatten=TRUE)
    cursor <- result$data$query[[method]]$pageInfo$endCursor
    res <- as.data.table(result$data$query[[method]]$edges)
    res[, cursor := NULL]
    
    print(i %+% " " %+% nrow(res))
    resList[[i]] <- res
    if (result$data$query[[method]]$pageInfo$hasNextPage == FALSE) break
  }
  res <- rbindlist(resList) %>% 
    setnames(names(res), gsub("node.", "", names(res)))
  res[, Date := as.Date(timestamp)]
  res[, collateral.id := subscanr::fixToken(collateral.id)]
  res <- merge(res, subscanr::tokens, by.x='collateral.id', by.y='Token')

  res[, adj := as.numeric(substr(as.character(1e20),1, as.numeric(decimals) + 1))]
  res[, depositAmount := as.numeric(depositAmount) / adj]
  res[, debitAmount := as.numeric(debitAmount) / adj]

  res[, depositVolumeUSD := as.numeric(depositVolumeUSD) / 1e18]
  res[, debitVolumeUSD := as.numeric(debitVolumeUSD) / 1e18]
  res[, depositChangedUSD := as.numeric(depositChangedUSD) / 1e18]
  res[, debitChangedUSD := as.numeric(debitChangedUSD) / 1e18]
  res[, debitExchangeRate := as.numeric(debitExchangeRate) / 1e18]
  res
  
}


getLoansDailyCollateralQuery <- function(endpoint, window) {
  method <- 'dailyCollaterals'; maxn <- 1000
  
  cli <- GraphqlClient$new(url = endpoint)
  mindate <- today(tzone = 'UTC') - window
  
  
  cursor <- ''
  resList <- list()
  for (i in 1:maxn) {
    if (cursor == '') {
      cursorStr <- 'first:100'
    } else {
      cursorStr <- 'first:100 after:"' %+% cursor %+% '"'
    }
    qry <- Query$new()
    qry$query(method, '
      {
        query {
          ' %+% method %+% ' (filter: {timestamp: {greaterThanOrEqualTo: "' %+% mindate %+% '"}} ' %+% cursorStr %+% ') {
            totalCount
            edges {
              node { 
               id collateral {id} depositAmount debitAmount depositVolumeUSD debitVolumeUSD 
               depositChangedUSD debitChangedUSD debitExchangeRate timestamp txCount
              }
              cursor
            }
            pageInfo {
              endCursor
              hasNextPage
            }
          }
        }
      }')
    result <- cli$exec(qry$queries[[method]])  %>%
      fromJSON(flatten=TRUE)
    cursor <- result$data$query[[method]]$pageInfo$endCursor
    res <- as.data.table(result$data$query[[method]]$edges)
    res[, cursor := NULL]
    
    print(i %+% " " %+% nrow(res))
    resList[[i]] <- res
    if (result$data$query[[method]]$pageInfo$hasNextPage == FALSE) break
  }
  res <- rbindlist(resList) %>% 
    setnames(names(res), gsub("node.", "", names(res)))
  res[, Date := as.Date(timestamp)]
  res[, collateral.id := subscanr::fixToken(collateral.id)]
  res <- merge(res, subscanr::tokens, by.x='collateral.id', by.y='Token')

  res[, adj := as.numeric(substr(as.character(1e20),1, as.numeric(decimals) + 1))]
  res[, depositAmount := as.numeric(depositAmount) / adj]
  res[, debitAmount := as.numeric(debitAmount) / adj]
  
  res[, depositVolumeUSD := as.numeric(depositVolumeUSD) / 1e18]
  res[, debitVolumeUSD := as.numeric(debitVolumeUSD) / 1e18]
  res[, depositChangedUSD := as.numeric(depositChangedUSD) / 1e18]
  res[, debitChangedUSD := as.numeric(debitChangedUSD) / 1e18]
  res[, debitExchangeRate := as.numeric(debitExchangeRate) / 1e18]
  res

}

getSwapsDex <- function(endpoint, window) {
  # endpoint <- dex_endpoint; window <- 10
  
  # make a client
  cli <- GraphqlClient$new(url = endpoint)
  mindate <- today(tzone = 'UTC') - window
  
  cursor <- ''
  resList <- list()
  for (i in 1:1000) {
    if (cursor == '') {
      cursorStr <- 'first:100'
    } else {
      cursorStr <- 'first:100 after:"' %+% cursor %+% '"'
    }
    qry <- Query$new()
    qry$query('swaps', '
    {
      query {
        swaps (filter: {timestamp: {greaterThanOrEqualTo: "' %+% mindate %+% '"}} ' %+% cursorStr %+% ') {
          totalCount
          edges {
            node { 
                id address {
                  id
                } pool {
                  id
                }  token0 {
                  id
                } token1 {
                  id
                } token0InAmount token1OutAmount
							  tradePath price0 price1 block {
							    id
							  } extrinsic {
							    id
							  } timestamp
            }
            cursor
          }
          pageInfo {
            endCursor
            hasNextPage
          }
        }
      }
    }')
    result <- cli$exec(qry$queries$swaps)  %>%
      fromJSON(flatten=TRUE)
    cursor <- result$data$query$swaps$pageInfo$endCursor
    res <- as.data.table(result$data$query$swaps$edges)
    res[, cursor := NULL]
    
    print(i %+% " " %+% nrow(res))
    resList[[i]] <- res
    if (result$data$query$swaps$pageInfo$hasNextPage == FALSE) break
  }
  res <- rbindlist(resList)
  setnames(res, old = names(res), new = gsub("node.", "", names(res)))
  
  if (substr(max(res$timestamp), 12, 13) < 23) {
    maxdate <- as.Date(max(res$timestamp))-1
  } else {
    maxdate <- as.Date(max(res$timestamp))
  }
  
  res <- res[timestamp <= maxdate]
  res[, date := as.Date(timestamp)]
  setorder(res, timestamp)
  
  # Replace foreign assets
  res[, token0.id := subscanr::fixToken(token0.id)]
  res[, token1.id := subscanr::fixToken(token1.id)]

  # Normalize pairs
  res[, pair := paste0(token0.id %+% ":" %+% token1.id)]
  res[token1.id < token0.id, pair := paste0(token1.id %+% ":" %+% token0.id)]
  res[, exclude := token0.id == token1.id]
  res  
  
}

# queries ####
dailyLoanCollateral <- getLoansDailyCollateralQuery(loan_endpoint, window)
dailyLoanPositions <- getLoansDailyPositionsQuery(loan_endpoint, window)
collaterParams <- getLoansCollateralParamsQuery(loan_endpoint)
liquidity <- liquidityQuery(swap_endpoint, window)
liquidity[, volumeUSD := as.numeric(volumeUSD) / 1e18]
liq <- liquidity[, .(date, pair, type, volumeUSD)]
liq2 <- liq[, .(.N, sum(volumeUSD)), by = .(pair, date, type)]
setnames(liq2, c("date","N","V2"), c("Date","Observations","volumeUSD"))
liq2[type == 'removeLiquidity', volumeUSD := -volumeUSD]


liq <- liq[, .(.N, sum(volumeUSD)), by = .(date, type)]
setnames(liq, c("date","N","V2"), c("Date","Observations","volumeUSD"))
liq[type == 'removeLiquidity', volumeUSD := -volumeUSD]

# swaps2 <- getSwapsByDay(swap_endpoint, window)
swaps2 <- getDailyPoolsQuery(dex_endpoint, window)
swaps <- getSwapsDex(dex_endpoint, window)
# swaps <- getSwaps(official_endpoint, window)
# swaps[, volumeUSDFloat := as.numeric(volumeUSD)]
# swaps[volumeUSDFloat < 0]
# swaps[volumeUSDFloat < 0, id]

swaps <- merge(swaps, subscanr::tokens, by.x='token0.id', by.y='Token') %>% setnames("decimals","decimals0")
swaps[, Name := NULL]
swaps <- merge(swaps, subscanr::tokens, by.x='token1.id', by.y='Token') %>% setnames("decimals","decimals1")
swaps[, Name := NULL]

swaps[, adj0 := as.numeric(substr(as.character(1e20),1, as.numeric(decimals0) + 1))]
swaps[, adj1 := as.numeric(substr(as.character(1e20),1, as.numeric(decimals1) + 1))]

swaps[, token0InAmount := as.numeric(token0InAmount)]
swaps[, token1OutAmount := as.numeric(token1OutAmount)]
swaps[, price0 := as.numeric(price0) / 1e18]
swaps[, price1 := as.numeric(price1) / 1e18]

swaps[, amount0 := token0InAmount / adj0]
swaps[, amount1 := token1OutAmount / adj1]

swaps[, token0.id := subscanr::fixToken(token0.id)]
swaps[, token1.id := subscanr::fixToken(token1.id)]
swaps[, tradePath := subscanr::fixToken(tradePath)]
swaps[, pathLength := length(strsplit(tradePath, ",")[[1]]) - 1, by = id]

swaps[, volume0USD := amount0 * price0]
swaps[, volume1USD := amount1 * price1]
swaps[, volumeUSDFloat := (volume0USD + volume1USD) / 2]
swaps[volume0USD == 0 & volume1USD > 0, volumeUSDFloat := volume1USD]
swaps[volume1USD == 0 & volume0USD > 0, volumeUSDFloat := volume0USD]

mysort <- function(a, b) ifelse(a < b, a %+% ":" %+% b, b %+% ":" %+% a)

getPath <- function(tradePath) {
  # tradePath <- swaps[1]$tradePath
  tp <- strsplit(tradePath, ",")[[1]]
  n <- length(tp) - 1
  if (n == 3) {
    return(list(mysort(tp[1],tp[2]), mysort(tp[2],tp[3]), mysort(tp[3],tp[4])))
  } else if (n == 2) {
    return(list(mysort(tp[1],tp[2]), mysort(tp[2],tp[3]), "NA:NA"))
  } 
  list(mysort(tp[1],tp[2]), "NA:NA", "NA:NA")
}
swaps[, c("pair1", "pair2", "pair3") := getPath(tradePath), by = id]

swaps[, fee := volumeUSDFloat * .03]
swaps[, feeAdj := volumeUSDFloat * .03 * pathLength]
setnames(swaps, "address.id", "accountId")
# setnames(swaps, "AccountId", "accountId")


pairs <- rbind(swaps[exclude == FALSE, .N, by = pair1] %>% setnames("pair1", "Pair"),
              swaps[exclude == FALSE, .N, by = pair2] %>% setnames("pair2", "Pair"),
              swaps[exclude == FALSE, .N, by = pair3] %>% setnames("pair3", "Pair"))
pairs <- pairs[, sum(N), by = Pair]
# remove pairs with NA in them
pairs <- pairs[-grep("NA", pairs$Pair)]
pairs <- rbind(data.table(Pair = "ALL", V1 = sum(pairs$V1)), pairs)
pairs <- pairs[order(V1, decreasing = TRUE)] %>%
  setnames(c("Pair", "Observations"))


tvl <- swaps2[date == max(date)]
stable_dex_pool_size <- tvl[grep("USD", tvl$pair), sum(abs(tvlUSD))]

# Calculate measures for each pair
user_status   <- list()
trades_status <- list()
tpu_status    <- list()
volume_status <- list()

users_list  <- list()
trades_list <- list()
per_list    <- list()
volume_list <- list()

# remove old params object before calling render with new params list
rm(params)
for (p in pairs$Pair) {
  # p <- pairs$Pair[1]
  
  try(rm(u_list, t_list, p_list, v_list), silent = TRUE)
  
  outname <- "~/R_HOME/websites/web_acala/content/swap_" %+% network %+% "_" %+% p %+% ".html"
  unlink(outname)
  # Create report for each pair
  rmarkdown::render("~/R_HOME/karura-reports/Swap_template.Rmd",
                  output_file = outname,
                  params = list(pair = p))
  
  # Store the data for the table
  user_status[p]   <- activeUsersStatus
  trades_status[p] <- tradesStatus
  tpu_status[p]    <- avgTradeStatus
  volume_status[p] <- tradeVolumeStatus
  
  users_list[[p]]  <- u_list
  trades_list[[p]] <- t_list
  per_list[[p]]    <- p_list
  volume_list[[p]] <- v_list

}

  d <- list()
  for (x in pairs$Pair) {
    d[x] <- paste0('', x, '', collapse = '')
  }

  inline_plot <- data.frame(Count = pairs$Observations, 
                            U_Growth = unlist(user_status),
                            U_Trend = "",
                            S_Growth = unlist(trades_status),
                            S_Trend = "",
                            TPU_Growth = unlist(tpu_status),
                            TPU_Trend = "",
                            V_Growth = unlist(volume_status),
                            V_Trend = "")
  row.names(inline_plot) <- unlist(d)

  getSpecColor <- function(x) {
    x[is.na(x)] <- 0
    ifelse(x < 1, "red", "green")
  }
  
  inline_plot$U_Growth <- cell_spec(inline_plot$U_Growth, color = getSpecColor(inline_plot$U_Growth))

  inline_plot$S_Growth <- cell_spec(inline_plot$S_Growth, color = getSpecColor(inline_plot$S_Growth))
  
  inline_plot$TPU_Growth <- cell_spec(inline_plot$TPU_Growth, color = getSpecColor(inline_plot$TPU_Growth))

  inline_plot$V_Growth <- cell_spec(inline_plot$V_Growth, color = getSpecColor(inline_plot$V_Growth))

  p <- inline_plot %>%
    kbl(booktabs = TRUE, escape = FALSE, align='rrrrrrrrr') %>%
    add_header_above(c(" " = 1, " " = 1, "Active Users" = 2, "Swap Trades" = 2, "Trades Per User" = 2, "Trading Volume" = 2)) %>%  
    kable_paper(full_width = FALSE) %>%
    column_spec(4, image = spec_plot(users_list, same_lim = FALSE)) %>%
    column_spec(6, image = spec_plot(trades_list, same_lim = FALSE)) %>%
    column_spec(8, image = spec_plot(per_list, same_lim = FALSE)) %>%
    column_spec(10, image = spec_plot(volume_list, same_lim = FALSE))

```

### `r network` Swap Performance Summary

```{r plot_acala, result='asis', out.height = 12}
p

```

Last updated: `r Sys.time()`

Date range of data: `r min(swaps$timestamp)` to `r max(swaps$timestamp)`.

Sources: 

* [SubQuery Network](https://explorer.subquery.network/)

Swaps:

* [Acala-Swap-Day-Data](https://api.subquery.network/sq/rogerjbos/acala-swap-day-data)

* [Karura-Swap-Day-Data](https://api.subquery.network/sq/rogerjbos/karura-swap-day-data)

Loans:

* [Acala-Loan-Data](https://api.subquery.network/sq/rogerjbos/acala-loan-subql)

* [Karura-Loan-Data](https://api.subquery.network/sq/rogerjbos/karura-loan-subql)